在昨天的文章中,我們探討幾種允許我們分別訪問物件key/value的方法,同時還能夠利用返回的陣列執行其他操作。
那for-in
總該是遍歷了吧?來看看MDN的說明:
The for...in statement iterates over all enumerable string properties of an object (ignoring properties keyed by symbols), including inherited enumerable properties.
上文重點:1. 可以迭代 2.只用在可列舉的屬性物件
for-in
可以用於迭代物件中的屬性,在MDN提到的基本語法如下:
for (variable in object) {
statement
}
//示範
const contestant = {
contestantId: 1,
contestantName: "Alice",
hotpotFlavor: "Spicy Sichuan",
hotpotIngredients: ["Beef slices", "Tofu", "Enoki mushrooms", "Napa cabbage"],
summarizeCooking: function () {
return "Balancing spicy Sichuan flavor with tender beef, tofu, and crunchy cabbage. Pairing with fragrant jasmine tea enhances the experience.";
},
}
for(const key in contestant){
console.log(key);
// 印出:
// contestantId
// contestantName
// hotpotFlavor
// hotpotIngredients
// summarizeCooking
}
那麽,對於變數可以使用哪些宣告關鍵字?var
、let
、const
都可以,或者在=
之前的宣告也有效,例如:
let key
for(key in contestant){
if (key === 'contestantId'){
continue;
}
console.log(key);
}
使用const
宣告之後一定要賦值,如果需要使用const
來宣告變數,就避免使用先宣告後再賦值的方式。此外可以使用continue
或break
跳過指定項目或中止循環。
在上面介紹for-in
的用法,會發現在MDN的列表上緊接介紹for-of
的使用,讓我們再看看for-of
的基本語法:
for (variable of iterable)
statement
從語法來看好像沒有太大區別,但會看到of後面有個iterable
,先不論這是什麽,我們試著讓物件應用在這個語法中:
for(const key of contestant){
console.log(key);
}//TypeError: contestant is not iterable
出錯了!回過頭來先看一下MDN如何寫for-of
可以使用哪些內容來遍歷:
The for-of statement executes a loop that operates on a sequence of values sourced from an iterable object.
因此,for-of
是用在可以被迭代的物件上面。
看到這大概會有疑問:
讓我們從以下開始解釋。
這個部分將涉及到迭代的相關知識,由於時間有限且已有人撰寫了詳細的解釋,因此在這裡我們將僅概述原因和解決方法,並列出前因後果。我們只專注在如何對物件使用for-of
的方式。
為了避免混淆,下文中使用{}
或new
建立的物件先稱作Ordinary Object
。
for-in是ES5的語法,是能遍歷鍵(key)的做法,ES6引入迭代(iteration)的遍歷數據的概念,以及for-of來遍歷value的語法,兩者搭配使用。
迭代使用的是名為可迭代(iterable)數據結構,使elements(元素)可以被訪問,並使用迭代器(iterator)來遍歷。
迭代涉及迭代協議(Iteration protocols)該協議定義可迭代跟迭代器的行為,特色是具有順序性,使迭代器一次只回傳一個值。迭代協議分別為可迭代協議(iterable protocol)與迭代器協議(iterator protocol)。
可迭代協議允許物件定義或自定義迭代行為,像是定義在for-of中可以迭代的對象,可以進行迭代行為的型別有:Arrays、Strings、Maps、Sets、DOM data structures,請記得Ordinary Object
不包含在內--,不然也不會有這段說明了--。要使Ordinary Object
成為可迭代的,需要實踐@@iterator
方法,並透過Symbol.iterator
讓物件獲得@@iterator
關鍵字的屬性
(再來看別人針對Ordinary Object
為何不是iterable的想法,我只能猜想在Javascript中被允許能夠迭代的型別是有順序性的,但Ordinary Object
並沒有這種順序性,最多只是可列舉的(Enumerable),所以我們想讓Ordinary Object
能夠被迭代,我們需要採取特定的方法,讓它具備順序性。)
next()
方法時,就會被視為一個迭代器,且迭代器是可以生成有限或無限的值,如果沒有設置一個中止條件,可能會無限生成。也就是說,其他物件已經內建Symbol.iterator()這個方法,需要迭代的時候可以直接拿來使用。
但是如果希望Ordinary Object
可以被for-of
使用,就需要在物件中設定Symbol.iterator()
跟next()
來設定回傳的方式。
如果想要自動迭代所有值,就使用for-of
。
如果想要手動迭代,就要使用自訂或內建的[Symbol.iterator]()
,並使用next()
逐個印出,印出的值會有兩個:done跟value。
以下使用Array示範:
const array = [1,2,3]
const log = array[Symbol.iterator]()
console.log(log.next());//{ value: 1, done: false }
console.log(log.next());//{ value: 2, done: false }
console.log(log.next());//{ value: 3, done: false }
console.log(log.next());//{ value: undefined, done: true }
value為物件的值,done則為布林值,false指的是尚未迭代結束,true則是完成,但就會發現需要把所有值都印出之後,下一輪才會真的顯示結束。
這次讓Ordinary Object
加上方法,結合Object.keys()
進行遍歷:
const contestant = {
contestantId: 1,
contestantName: "Alice",
hotpotFlavor: "Spicy Sichuan",
hotpotIngredients: ["Beef slices", "Tofu", "Enoki mushrooms", "Napa cabbage"],
summarizeCooking: function () {
return "Balancing spicy Sichuan flavor with tender beef, tofu, and crunchy cabbage. Pairing with fragrant jasmine tea enhances the experience.";
},
//回傳一個符合迭代器協議的物件,也就是這個物件本身
[Symbol.iterator]() {
this.index = 0;
return this
},
//針對上面的方法設置印出的內容
next(){
//如果沒有設置中止條件就會一直印出結果,故使用判斷式判斷中止條件
if (this.index<Object.keys(this).length){
//keys = //['contestantId','contestantName','hotpotFlavor','hotpotIngredients','summarizeCooking','next','index']
const keys = Object.keys(this);
//key = keys[0]、keys[1]...
const key = keys[`${this.index}`]
//index = 1、2...
this.index++
//value = contestant的key[0]、key[1]...
return {done:false,value:this[key]}
} else {
return {done:true};
}
}
}
for(const content of contestant){
console.log(content);
}
// 印出結果:
// 1
// Alice
// Spicy Sichuan
// [ 'Beef slices', 'Tofu', 'Enoki mushrooms', 'Napa cabbage' ]
// [Function: summarizeCooking]
// [Function: next]
// 7
在這個問題之前,我們再來看某個東西:
//使用最上面的範例
const contestant = {
contestantId: 1,
contestantName: "Alice",
hotpotFlavor: "Spicy Sichuan",
hotpotIngredients: ["Beef slices", "Tofu", "Enoki mushrooms", "Napa cabbage"],
summarizeCooking: function () {
return "Balancing spicy Sichuan flavor with tender beef, tofu, and crunchy cabbage. Pairing with fragrant jasmine tea enhances the experience.";
},
}
//對這個物件設置原型(注意:此程式碼只是範例,不建議在原有的物件上額外增加原型,可參考MDN對Object.setPrototypeOf()的說明)
const addNewItem = { fruit:'apple',};
Object.setPrototypeOf(contestant,addNewItem);
//新增原型之後
console.log(contestant);
//印出:
//{
// contestantId: 1,
// contestantName: 'Alice',
// hotpotFlavor: 'Spicy Sichuan',
// hotpotIngredients: [ 'Beef slices', 'Tofu', 'Enoki mushrooms', 'Napa cabbage' ],
// summarizeCooking: [Function: summarizeCooking]
// }
for(const key in contestant){
console.log(key);
//印出:
// contestantId
// contestantName
// hotpotFlavor
// hotpotIngredients
// summarizeCooking
// fruit
}
就會發現當我們新增原型之後,for-in
會連同原型的key一起印出來。因此如果有需要的話,建議使用昨天提到object的method,就會只印出名稱……但說到底,這個prototype
到底是什麽?可列舉又是什麽?
之後再揭曉。
Why are Objects not Iterable in JavaScript?
JavaScript iterators and generators: A complete guide
認識 JavaScript Iterable 和 Iterator
21. Iterables and iterators
Iterator 和 for...of 循环
迭代器(Iterator)